package org.dfl.core.data.types;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * class to manage types
 * @author Piotr Wlodarczyk
 *
 */
public class TypeManager {
	protected static String STRING_TYPE = "String";
	protected static String BOOLEAN_TYPE = "Boolean";
	protected static String BINARY_TYPE = "Bytes";

	public static SimpleType getSimpleAnyType(){
		return new SimpleType(null);
	}
	public static boolean isSimpleAnyType(Type t){
		return (isSimpleType(t) && ((SimpleType) t).getTypeStr()==null);
	}
	public static boolean isSimpleType(Type t){
		return (t instanceof SimpleType );
	}
	
	// string
	public static SimpleType getStringType(){
		return new SimpleType(STRING_TYPE);
	}
	public static boolean isStringType(Type t){
		return (t instanceof SimpleType && STRING_TYPE.equals(((SimpleType) t).getTypeStr()));
	}
	
	// boolean
	public static SimpleType getBooleanType(){
		return new SimpleType(BOOLEAN_TYPE);
	}
	public static boolean isBooleanType(Type t){
		return (t instanceof SimpleType && BOOLEAN_TYPE.equals(((SimpleType) t).getTypeStr()));
	}
	
	//binary
	public static SimpleType getBinaryType(){
		return new SimpleType(BINARY_TYPE);
	}
	public static boolean isBinaryType(Type t){
		return (t instanceof SimpleType && BINARY_TYPE.equals(((SimpleType) t).getTypeStr()));
	}
	
	//collection
	public static CollectionType getCollectionType(){
		return new CollectionType();
	}
	public static CollectionType getCollectionType(Type subtype){
		return new CollectionType(subtype);
	}
	public static boolean isCollectionType(Type t){
		return (t instanceof CollectionType);
	}
	
	//map
	public static MapType getMapType(){
		return new MapType();
	}
	public static boolean isMapType(Type t){
		return (t instanceof MapType);
	}
	
	//	 any/empty
	public static AnyType getAnyType(){
		return new AnyType();
	}
	public static boolean isAnyType(Type t){
		return (t instanceof AnyType);
	}
	
	/**
	 * convert taverna string type to dfl type
	 * @param tavernaString
	 * @return type
	 */
	public static Type getTavernaType(String tavernaString) {
		tavernaString = tavernaString.toLowerCase();
		if (tavernaString.equals("'text/xml'"))
			return getStringType();
		else if (tavernaString.equals("'text/plain'"))
			return getStringType();
		else if (tavernaString.equals("'application/octet-stream'"))
			return getBinaryType();
		else if (tavernaString.equals("'image/*'"))
			return getBinaryType();
		else if(tavernaString.startsWith("l(")){
			return getCollectionType(getTavernaType(tavernaString.substring(2,tavernaString.length()-1)));
		}
		return getStringType();
	}
	
	/**
	 * load a type from its string representation
	 * @param typeStr - string representation of a type
	 * @return type
	 */
	public static Type load(String typeStr) {
		// collection [ ]
		if(typeStr.substring(0,1).equals("{")){
			typeStr = typeStr.substring(1, typeStr.length()-1);
			return getCollectionType(load(typeStr));
		}else if(typeStr.substring(0,1).equals("<")){
			typeStr = typeStr.substring(1, typeStr.length()-1);
			
			MapType m = (MapType) getMapType();
			String[] subtypes = typeStr.split(",");
			if(subtypes!=null && subtypes.length>0)
			for(int i=0;i<subtypes.length;i++){
				String[] st = subtypes[i].split(":");
				if(st!=null && st.length>=2)
				m.addSubkey(st[0],load(st[1].trim()));
			}
			return m;
		}else if(STRING_TYPE.equals(typeStr)){
			return getStringType();
		}else if(BINARY_TYPE.equals(typeStr)){
			return getBinaryType();
		}else if(BOOLEAN_TYPE.equals(typeStr)){
			return getBooleanType();
		}else{
			// jesli nie wiadomo, co to jest, to jest to cokolwiek :)
			return getAnyType();
		}
	}	
	
	@SuppressWarnings("unchecked")
	public static boolean isValidDataElem(Object root, Type t){
		if(root == null || t ==null){
			return false;
		}
		if(isAnyType(t)){
			return true;
		}
		
		if(TypeManager.isStringType(t) && root instanceof String){ //$NON-NLS-1$
			return true;
		}else if(TypeManager.isBooleanType(t) && root instanceof Boolean){
			return true;
		}else if(TypeManager.isBinaryType(t) && root instanceof byte[]){ //$NON-NLS-1$
			return true;
		}else if(TypeManager.isCollectionType(t) && root instanceof Collection){ //$NON-NLS-1$
			Collection children = (Collection) root;
			for(Object child : children){
				if(!isValidDataElem(child,((CollectionType)t).getSubtypeContainer().getType())){
					return false;
				}
			}
			return true;
		}else if(TypeManager.isMapType(t) && root instanceof Map){ //$NON-NLS-1$
			MapType mt = (MapType) t;
			Map m = (Map) root;
			Set keys = m.keySet();
			for(Object key: keys){
				if(!(key instanceof String)){
					return false;
				}
				if(mt.getSubtype((String)key)!=null && !isValidDataElem(m.get(key), mt.getSubtype(((String)key)).getType())){
					return false;
				}
			}
			return true;
		}else{
			return false;
		}
	}
}
